/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package com.googlecode.ivybeans.module.classpath;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.netbeans.api.java.classpath.ClassPath;

import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.java.queries.UnitTestForSourceQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.spi.java.classpath.ClassPathFactory;
import org.netbeans.spi.java.classpath.ClassPathImplementation;
import org.netbeans.spi.java.classpath.ClassPathProvider;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;

/**
 * 
 * @author lforet
 */
public class ClassPathProviderImpl implements ClassPathProvider, PropertyChangeListener {

    private static enum FileType {

        SOURCE, TEST_SOURCE, UNKNOWN
    };
    private final ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();
    private final Lock rlock = reentrantLock.readLock();
    private final Lock wlock = reentrantLock.writeLock();
    private final Project project;
    private List<FileObject> sourceRoots;
    private List<FileObject> testSourceRoots;
    private Map<ClassPathScope, ClassPath> cache = new HashMap<ClassPathScope, ClassPath>();

    public ClassPathProviderImpl(Project project) {
        this.project = project;
    }

    public ClassPath findClassPath(FileObject fileObject, String type) {
        final FileType fileType = getFileType(fileObject);
        switch (fileType) {
            case SOURCE: {
                if (ClassPath.SOURCE.equals(type) || ClassPath.COMPILE.equals(type)) {
                    return getClassPath(ClassPathScope.COMPILE);
                } else if (ClassPath.EXECUTE.equals(type)) {
                    return getClassPath(ClassPathScope.RUNTIME);
                }
                break;
            }
            case TEST_SOURCE: {
                if (ClassPath.SOURCE.equals(type) || ClassPath.COMPILE.equals(type)) {
                    return getClassPath(ClassPathScope.COMPILE_TEST);
                } else if (ClassPath.EXECUTE.equals(type)) {
                    return getClassPath(ClassPathScope.RUNTIME_TEST);
                }
            }
        }
        return null;
    }

    public synchronized ClassPath getClassPath(ClassPathScope scope) {
        ClassPath cp = cache.get(scope);
        if (cp == null) {
            ScopedClassPathImpl scpi = new ScopedClassPathImpl(project, scope);
            cp = ClassPathFactory.createClassPath(scpi);
            scpi.addPropertyChangeListener(this);
            cache.put(scope, cp);
        }
        return cp;
    }

    public void propertyChange(PropertyChangeEvent evt) {
        if (ClassPathImplementation.PROP_RESOURCES.equals(evt.getPropertyName())) {
            clearCache();
        }
    }

    private FileType getFileType(FileObject fo) {
        rlock.lock();
        try {
            if (sourceRoots == null) {
                try {
                    rlock.unlock();
                    wlock.lock();
                    SourceGroup[] allJavaSources = ProjectUtils.getSources(project).getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
                    sourceRoots = new ArrayList<FileObject>();
                    testSourceRoots = new ArrayList<FileObject>();
                    for (SourceGroup sourceGroup : allJavaSources) {
                        FileObject sourceFO = sourceGroup.getRootFolder();
                        if (UnitTestForSourceQuery.findSources(sourceFO).length > 0) {
                            testSourceRoots.add(sourceFO);
                        } else {
                            sourceRoots.add(sourceFO);
                        }
                    }
                } finally {
                    rlock.lock();
                    wlock.unlock();
                }
            }

            for (FileObject sourceFO : sourceRoots) {
                if (sourceFO.equals(fo) || FileUtil.isParentOf(sourceFO, fo)) {
                    return FileType.SOURCE;
                }
            }
            for (FileObject testSourceFO : testSourceRoots) {
                if (testSourceFO.equals(fo) || FileUtil.isParentOf(testSourceFO, fo)) {
                    return FileType.TEST_SOURCE;
                }
            }
            return FileType.UNKNOWN;
        } finally {
            rlock.unlock();
        }
    }

    private void clearCache() {
        cache = new HashMap<ClassPathScope, ClassPath>();
    }
}
